package com.canaan.common.utils;


import cn.hutool.core.date.format.FastDateFormat;
import cn.hutool.core.util.NumberUtil;
import com.canaan.common.annotation.TableName;
import com.canaan.common.exception.ServiceException;
import com.canaan.common.utils.spring.SpringUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;


public class TableNoUtil {
    private static JdbcTemplate jdbcTemplate = SpringUtils.getBean(JdbcTemplate.class);
    private static StringRedisTemplate redisTemplate = SpringUtils.getBean(StringRedisTemplate.class);
    private static final String SERIAL_NO_GENERATE_KEY = "serialNo:tableColumn:%s";
    private static final String QUERY_LATEST_DAY_NO_SQL = "SELECT %s FROM %s WHERE DATE(CREATE_TIME) >= CURDATE() AND del_flag = 0 ORDER BY %s DESC LIMIT 1";


    public static String getNo(Class<?> c) {
        TableName annotation = c.getAnnotation(TableName.class);
        if (annotation == null) {
            new ServiceException("表名不存在");
        }
        String tableName = Optional.ofNullable(annotation.value()).
                orElseThrow(() -> new ServiceException("表名不存在"));
        return generateSerialNoByDay(tableName, "no");
    }

    private static Map<String, String> lockMap = new ConcurrentHashMap<>();

    public synchronized static String getLock(String tableName) {
        lockMap.putIfAbsent(tableName, tableName);
        return lockMap.get(tableName);
    }

    /**
     * 按天生成流水号
     *
     * @param table  数据表名称
     * @param column 数据库中标号字段（如toolNo）
     * @return 流水号
     **/
    public static String generateSerialNoByDay(String table, String column) {
        synchronized (getLock(table)) {
            Long number = 0L;
            FastDateFormat df = FastDateFormat.getInstance("yyyyMMddHH");
            String today = df.format(new Date());
            String serialGenerateKey = getSerialGenerateKey(table, column);
            int numberLength = 6;

            if (BooleanUtils.isNotTrue(redisTemplate.hasKey(serialGenerateKey))) {
                // 查找数据库最后一天数据得到编号进行判断
                String serialNo = getLatestSerialNoByDay(column, table);
                if (StringUtils.isNotEmpty(serialNo) && NumberUtil.isNumber(serialNo)) {
                    number = Long.valueOf(serialNo);
                } else {
                    number = Long.valueOf(today + "000001");
                }
                // 放到redis缓存上
                redisTemplate.opsForValue().set(serialGenerateKey, number.toString());
            }
            // redis自增
            number = redisTemplate.opsForValue().increment(serialGenerateKey);
            if (!(number + "").contains(today)) {
                number = Long.valueOf(today + "000001");
                // 放到redis缓存上
                redisTemplate.opsForValue().set(serialGenerateKey, number.toString());
            }
            // 过期时间设置为1天
            redisTemplate.expire(serialGenerateKey, 1L, TimeUnit.DAYS);
            return getSerialNo(number, numberLength);
        }
    }


    private static String getSerialGenerateKey(String table, String column) {
        return String.format(SERIAL_NO_GENERATE_KEY, table + "." + column);
    }

    /**
     * 5位流水号，补零
     *
     * @param number 数字
     * @return 流水号
     */
    public static String getSerialNo(Long number, int size) {
        return String.format("%0" + size + "d", number);
    }

    /**
     * 获取当天最新的流水号
     *
     * @param column 列名
     * @param table  表名
     * @return 流水号
     */
    private static String getLatestSerialNoByDay(String column, String table) {
        String queryLatestDayNoSql = getQueryLatestDayNoSql(column, table);
        // 通过Spring自带的JdbcTemplate类来查询数据库中的数据
        try {
            return jdbcTemplate.queryForObject(queryLatestDayNoSql, String.class);
        } catch (EmptyResultDataAccessException e) {
            return StringUtils.EMPTY;
        } catch (Exception e) {

            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
    }

    private static String getQueryLatestDayNoSql(String column, String table) {
        return String.format(QUERY_LATEST_DAY_NO_SQL, column, table, column);
    }

}
